﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OpenTK.Graphics.OpenGL;
using OpenTK;
using SimpleModeler.Materials;
using SimpleModeler.Textures;
using System.Drawing;
using Glu = OpenTK.Graphics.Glu;

namespace SimpleModeler.Graphics
{
    /// <summary>
    /// Stellt ein renderbares Szenenobjekt dar.
    /// </summary>
    [Serializable()]
    public abstract class SceneObject : ICloneable, IDisposable
    {
        #region Fields
        private Vector3 _size;
        private Vector3 _rotation;
        #endregion // Fields


        #region Properties
        /// <summary>
        /// Eindeutige ID des Objektes
        /// </summary>
        public ID ID { get; private set; }
        /// <summary>
        /// Gibt an, ob das Objekt selektiert ist
        /// </summary>
        public bool IsSelected { get; set; }
        /// <summary>
        /// Die Farbe des Objektes
        /// </summary>
        public Color Color { get; set; }
        /// <summary>
        /// Die Größe des Objektes
        /// </summary>
        public Vector3 Size
        {
            get { return _size; }
            set
            {
                if (value.X > 0.1f && value.Y > 0.1f && value.Z > 0.1f)
                    this._size = value;
            }
        }
        /// <summary>
        /// Die Position des Objektes im Raum
        /// </summary>
        public Vector3 Coordinate { get; set; }
        /// <summary>
        /// Das Material des Objektes
        /// </summary>
        public Material Material { get; set; }
        /// <summary>
        /// Die TexturID des Objektes
        /// </summary>
        public uint TextureID { get; set; }
        /// <summary>
        /// Die Textur des Objektes
        /// </summary>
        public Texture Texture { get; set; }
        /// <summary>
        /// Die Rotation des Objektes
        /// </summary>
        public Vector3 Rotation
        {
            get { return _rotation; }
            set { _rotation = new Vector3(value.X % 360, value.Y % 360, value.Z % 360); }
        }
        /// <summary>
        /// Koordinaten, wo das Objekt gezeichnet werden soll
        /// </summary>
        protected virtual Vector3 DrawCoordinate { get { return Coordinate; } }
        #endregion // Properties


        #region Constructors
        /// <summary>
        /// Konstruktor zum Klonen von Objeken
        /// </summary>
        /// <param name="id">ID des neuen Objektes</param>
        protected SceneObject(ID id) { this.ID = id; }

        /// <summary>
        /// Konstruktor
        /// </summary>
        public SceneObject() : this(Color.White) { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="color">Farbe des Objektes</param>
        public SceneObject(Color color) : this(color, new Vector3(1, 1, 1)) { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="color">Farbe des Objektes</param>
        /// <param name="size">Größe des Objektes</param>
        public SceneObject(Color color, Vector3 size) : this(color, size, new Vector3(0,0,0)) { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="color">Farbe des Objektes</param>
        /// <param name="size">Größe des Objektes</param>
        /// <param name="coordinate">Raum-Koordinaten für das Objekt</param>
        public SceneObject(Color color, Vector3 size, Vector3 coordinate) : this(color, size, coordinate, null, null) { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="color">Farbe des Objektes</param>
        /// <param name="size">Größe des Objektes</param>
        /// <param name="coordinate">Raum-Koordinaten für das Objekt</param>
        /// <param name="texture">Textur des Objektes</param>
        public SceneObject(Color color, Vector3 size, Vector3 coordinate, Texture texture) : this(color, size, coordinate, null, texture) { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="color">Farbe des Objektes</param>
        /// <param name="size">Größe des Objektes</param>
        /// <param name="coordinate">Raum-Koordinaten für das Objekt</param>
        /// <param name="material">Material des Objektes</param>
        public SceneObject(Color color, Vector3 size, Vector3 coordinate, Material material) : this(color, size, coordinate, material, null) { }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="color">Farbe des Objektes</param>
        /// <param name="size">Größe des Objektes</param>
        /// <param name="coordinate">Raum-Koordinaten für das Objekt</param>
        /// <param name="material">Material des Objektes</param>
        /// <param name="texture">Textur des Objektes</param>
        public SceneObject(Color color, Vector3 size, Vector3 coordinate, Material material, Texture texture)
        {
            this.ID = ID.NextID;
            this.Color = color;
            this.Size = size;
            this.Coordinate = coordinate;
            this.Material = material;
            this.Texture = texture;
        }
        #endregion // Constructors


        #region Methods
        #region Render-Methods
        /// <summary>
        /// Rendert das Objekt für den PickingMode
        /// </summary>
        public void RenderPicking() { Render(true); }

        /// <summary>
        /// Rendert das Objekt so wie es sichtbar wird
        /// </summary>
        public virtual void RenderFrame() { Render(false); }
        
        /// <summary>
        /// Rendert das Objekt
        /// </summary>
        /// <param name="forPicking">Wenn true, wird für den PickingMode gerendert</param>
        public virtual void Render(bool forPicking)
        {
            GL.PushMatrix();

            // Alles größen und Positions unabhängige zeichen
            if (!forPicking && IsSelected)
                RenderLinksToAxis();
            
            // Skalieren und bewegen
            //GL.Translate(this.Coordinate); // Zu den Koordinaten bewegen
            GL.Translate(this.DrawCoordinate); // Zu den Koordinaten bewegen

            if (IsSelected) // Wenn das Objekt selektiert ist werden die Verbindungen zu den Achsen angezeigt
                RenderObjectAxis(); //normals auf das object selbst

            GL.Scale(this.Size);

            GL.Rotate(Rotation.X, 1, 0, 0);
            GL.Rotate(Rotation.Y, 0, 1, 0);
            GL.Rotate(Rotation.Z, 0, 0, 1);
            // AB HIER ALLES POSITIONS- UND SKALIERUNGSABHÄNGIGE ZEICHNEN


            if (forPicking)
            {// Wenn picking-Mode, dann die Farbe auf ID setzen
                GL.Color4(this.ID.Color);
            }
            else
            {// andernfalls in der angegebenen Farbe zeichnen
                GL.Color4(this.Color);
                if (this.Texture != null && this.Texture.Loaded && !GL.IsEnabled(EnableCap.Texture2D))
                    GL.Enable(EnableCap.Texture2D);
            }

            // Das Objekt zeichnen
            RenderVertices();
            if (GL.IsEnabled(EnableCap.Texture2D))
                GL.Disable(EnableCap.Texture2D);

            GL.PopMatrix();
        }

        /// <summary>
        /// Rendern der einzelnen Vertices für das Objekt
        /// </summary>
        protected abstract void RenderVertices();

        /// <summary>
        /// Rendert die Verlängerungen zu den Koordinaten-System-Achsen
        /// </summary>
        protected void RenderLinksToAxis()
        {
            GL.Color3(Color.Gray);
            // X
            GL.Begin(BeginMode.Lines);
            GL.Vertex3(0.0f, 0.0f, Coordinate.Z);
            GL.Vertex3(Coordinate.X, 0.0f, Coordinate.Z);
            GL.End();

            // Y
            GL.Begin(BeginMode.Lines);
            GL.Vertex3(Coordinate.X, 0.0f, Coordinate.Z);
            GL.Vertex3(Coordinate.X, Coordinate.Y, Coordinate.Z);
            GL.End();

            // Z
            GL.Begin(BeginMode.Lines);
            GL.Vertex3(Coordinate.X, 0.0f, 0.0f);
            GL.Vertex3(Coordinate.X, 0.0f, Coordinate.Z);
            GL.End();
        }

        /// <summary>
        /// Rendert die Achsen für das Objekt
        /// </summary>
        protected void RenderObjectAxis()
        {
            IntPtr con = Glu.NewQuadric();
            float cBase = 0.2f;
            float cTop = 0.0f;
            float cHeight = 0.6f;
            int cStacks = 30;
            int cSlices = 30; 
            GL.Color3(Color.Gray);

            // X
            GL.Color3(Color.Red);
            GL.Begin(BeginMode.Lines);
            GL.Vertex3(-Size.X * 2.0f, 0.0f, 0.0f);
            GL.Vertex3(Size.X * 2.0f, 0.0f, 0.0f);
            GL.End();

            GL.Translate(new Vector3(-Size.X * 2.0f + cHeight, 0.0f, 0.0f));
            GL.Rotate(270, new Vector3(0, 1, 0));
            Glu.Cylinder(con, cBase, cTop, cHeight, cSlices, cStacks);
            GL.Rotate(-270, new Vector3(0, 1, 0));
            GL.Translate(new Vector3(Size.X * 2.0f - cHeight, 0.0f, 0.0f));

            GL.Translate(new Vector3(Size.X * 2.0f - cHeight, 0.0f, 0.0f));
            GL.Rotate(90, new Vector3(0, 1, 0));
            Glu.Cylinder(con, cBase, cTop, cHeight, cSlices, cStacks);
            GL.Rotate(-90, new Vector3(0, 1, 0));
            GL.Translate(new Vector3(-Size.X * 2.0f + cHeight, 0.0f, 0.0f));

            // Y
            GL.Color3(Color.Green);
            GL.Begin(BeginMode.Lines);
            GL.Vertex3(0.0f, -Size.Y * 2.0f, 0.0f);
            GL.Vertex3(0.0f, Size.Y * 2.0f, 0.0f);
            GL.End();

            GL.Translate(new Vector3(0.0f, -Size.Y * 2.0f + cHeight, 0.0f));
            GL.Rotate(90, new Vector3(1, 0, 0));
            Glu.Cylinder(con, cBase, cTop, cHeight, cSlices, cStacks);
            GL.Rotate(-90, new Vector3(1, 0, 0));
            GL.Translate(new Vector3(0.0f, Size.Y * 2.0f - cHeight, 0.0f));

            GL.Translate(new Vector3(0.0f, Size.Y * 2.0f - cHeight, 0.0f));
            GL.Rotate(270, new Vector3(1, 0, 0));
            Glu.Cylinder(con, cBase, cTop, cHeight, cSlices, cStacks);
            GL.Rotate(-270, new Vector3(1, 0, 0));
            GL.Translate(new Vector3(0.0f, -Size.Y * 2.0f + cHeight, 0.0f));

            // Z
            GL.Color3(Color.Blue);
            GL.Begin(BeginMode.Lines);
            GL.Vertex3(0.0f, 0.0f, -Size.Z * 2.0f);
            GL.Vertex3(0.0f, 0.0f, Size.Z * 2.0f);
            GL.End();

            GL.Translate(new Vector3(0.0f, 0.0f, -Size.Z * 2.0f + cHeight));
            GL.Rotate(180, new Vector3(0, 1, 0));
            Glu.Cylinder(con, cBase, cTop, cHeight, cSlices, cStacks);
            GL.Rotate(-180, new Vector3(0, 1, 0));
            GL.Translate(new Vector3(0.0f, 0.0f, Size.Z * 2.0f - cHeight));

            GL.Translate(new Vector3(0.0f, 0.0f, Size.Z * 2.0f - cHeight));
            Glu.Cylinder(con, cBase, cTop, cHeight, cSlices, cStacks);
            GL.Translate(new Vector3(0.0f, 0.0f, -Size.Z * 2.0f + cHeight));

            Glu.DeleteQuadric(con);
        }
        #endregion // Render-Methods

        /// <summary>
        /// Stringrepräsentation
        /// </summary>
        /// <returns>Stringrepräsentation</returns>
        public override string ToString() { return String.Format("{0}\nID: {1}", this.GetType().Name, this.ID); }

        #region Cloneable-Implementation
        /// <summary>
        /// Erstellt eine Kopie des Objektes
        /// </summary>
        /// <returns>Kopie des Objektes</returns>
        public abstract object Clone();

        /// <summary>
        /// Kopiert die SceneObject-Parameter
        /// </summary>
        /// <param name="so">Objekt, auf welche die Parameter übertragen werden sollen</param>
        protected void CloneParameters(ref SceneObject so)
        {
            so.Color = this.Color;
            so.Coordinate = this.Coordinate;
            so.ID = this.ID;
            so.IsSelected = this.IsSelected;
            so.Material = this.Material;
            so.Size = this.Size;
            so.Texture = this.Texture;
        }
        #endregion // Cloneable-Implementation


        #region Disposable-Implementation
        /// <summary>
        /// Gibt Ressourcen frei
        /// </summary>
        public virtual void Dispose() { }
        #endregion // Disposable-Implementation
        #endregion // Methods
    }
}
